# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: api\handlers\frontend\target_groups.py
__author__ = "sanyi"
import logging, uuid, sqlalchemy.exc, sqlalchemy.sql.expression
from sqlalchemy import func as sql_func
from sqlalchemy import not_ as sql_not
from sqlalchemy import select as sql_select
from api.classes.frontend.auto import *
from api.filters import Filters
from api.filters.sqlalchemy_adapter import get_sqlachemy_filter
from api.handlers.frontend import FrontendResourceHandler
from db import Connection
from db.data.access import generate_group_query, generate_target_query, apply_filters
from db.data.users import (
    require_at_least_tech_admin,
    require_at_least_tech_admin_with_access_all,
    get_owner_id,
)
from db.tables.events import create_event, EventResources
from db.tables.groups import GroupsTable, GroupRow
from db.tables.target2group import Target2GroupRow, Target2GroupTable
from db.tables.targets import TargetRow, TargetsTable
from db.tables.user2group import User2GroupTable, User2GroupRow
from server.http_responses.http_status_codes import Http201, Http204
from server.http_responses.http_errors import Http403, Http404, Http409
from helpers.licensing.features import Features

logger = logging.getLogger("frontend_api_resources.TargetGroups")
group_filters = {
    "keys": {
        "user_id": {"value_type": "uuid4", "multiple_values": True},
        "group_id": {"value_type": "uuid4", "multiple_values": True},
        "target_id": {"value_type": "uuid4", "multiple_values": True},
        "name": {
            "value_type": "string",
            "max_len": 256,
            "multiple_values": False,
            "operators": ("ilike_op",),
        },
    }
}


class TargetGroups(FrontendResourceHandler):
    def get_groups(self, l=None, c=None, q=None, **_):
        """
        Permissions: any_role & (access_all | access_group)
        :param l:
        :param c:
        :param q:
        :param _:
        :return:
        """
        self.license.require(Features.TARGET_GROUPS)
        limit = min(l, 100) if l is not None else 100
        c = int(c) if c is not None else 0
        cursor = c
        filters = Filters(group_filters, q)
        joined_tables = []
        select_from, tail_filters = generate_group_query(
            self.request_user, joined_tables
        )
        select_from = select_from.outerjoin(
            Target2GroupTable, Target2GroupRow.group_id == GroupRow.group_id
        )
        joined_tables.append(Target2GroupTable)
        for filter_key, filter_expression in filters.data.items():
            flt = None
            if filter_key == "user_id":
                flt = get_sqlachemy_filter(
                    filter_expression,
                    User2GroupRow.user_id,
                    filters.descriptor["keys"][filter_key],
                )
                if flt is not None:
                    if User2GroupTable not in joined_tables:
                        select_from = select_from.outerjoin(
                            User2GroupTable, User2GroupRow.group_id == GroupRow.group_id
                        )
                        joined_tables.append(User2GroupTable)
            else:
                if filter_key == "group_id":
                    flt = get_sqlachemy_filter(
                        filter_expression,
                        GroupRow.group_id,
                        filters.descriptor["keys"][filter_key],
                    )
                else:
                    if filter_key == "target_id":
                        flt = get_sqlachemy_filter(
                            filter_expression,
                            Target2GroupRow.target_id,
                            filters.descriptor["keys"][filter_key],
                        )
                    else:
                        if filter_key == "name":
                            flt = get_sqlachemy_filter(
                                filter_expression,
                                GroupRow.name,
                                filters.descriptor["keys"][filter_key],
                            )
            if flt is not None:
                tail_filters.append(flt)

        query = sql_select(
            (GroupRow, sql_func.count(Target2GroupRow.target_id).label("target_count"))
        ).select_from(select_from)
        for tail_filter in tail_filters:
            query = query.where(tail_filter)

        query = query.group_by(GroupRow.group_id)
        query = (
            query.limit(limit).offset(cursor).order_by(GroupRow.name, GroupRow.group_id)
        )
        groups = []
        with Connection(self.current_shard_name) as (connection):
            for group in connection.execute(query).fetchall():
                groups.append(ClsTargetGroup(**dict(group)))

        l = len(groups)
        if l >= limit:
            next_cursor = cursor + l
        else:
            next_cursor = None
        return ClsTargetGroupsListResponse(
            groups=groups,
            pagination=ClsPagination(previous_cursor=cursor, next_cursor=next_cursor),
        )

    def get_group(self, group_id, **_):
        """
        Permissions: any_role & (access_all | access_group)
        :param group_id:
        :param _:
        :return:
        """
        self.license.require(Features.TARGET_GROUPS)
        select_from, filters = generate_group_query(self.request_user)
        select_from = select_from.outerjoin(
            Target2GroupTable, Target2GroupRow.group_id == GroupRow.group_id
        )
        select_from = select_from.outerjoin(
            TargetsTable, TargetRow.target_id == Target2GroupRow.target_id
        )
        filters.append(TargetRow.deleted_at.is_(None))
        query = sql_select(
            (
                GroupsTable,
                sql_func.count(Target2GroupRow.target_id).label("target_count"),
            )
        ).select_from(select_from)
        query = apply_filters(query, filters)
        query = (
            query.where(GroupRow.group_id == group_id)
            .where(TargetRow.deleted_at.is_(None))
            .group_by(GroupRow.group_id)
        )
        with Connection(self.current_shard_name) as (connection):
            group = connection.execute(query).fetchone()
            if group:
                return ClsTargetGroup.from_dict(dict(group))
            return Http404()

    def add_group(self, body, **_):
        """
        Permissions: tech_admin & access_all
        :param body:
        :param _:
        :return:
        """
        self.license.require(Features.TARGET_GROUPS)
        require_at_least_tech_admin_with_access_all(self.request_user)
        owner_id = get_owner_id(self.request_user)
        group = ClsTargetGroup(**body)
        group.group_id = uuid.uuid4().hex
        with Connection(self.current_shard_name) as (connection):
            query = GroupsTable.insert().values(
                group_id=group.group_id,
                name=group.name,
                description=group.get("description"),
                owner_id=owner_id,
                creator_id=self.request_user["user_id"],
            )
            try:
                connection.execute(query)
            except sqlalchemy.exc.IntegrityError as e:
                if "ix_groups_unique_name" in str(e):
                    raise Http409("Group name should be unique")

            event_query = create_event(
                "group_created",
                get_owner_id(self.request_user),
                self.request_user["user_id"],
                data=group.to_dict(),
                resource_type=EventResources.group,
                resource_id=group.group_id,
                shard=self.current_shard_name,
            )
            connection.execute(event_query)
            return Http201(
                location=self.base_path + "/target_groups/" + group.group_id, body=group
            )

    def delete_group(self, group_id, **_):
        """
        Permissions: tech_admin & access_all
        :param group_id:
        :param _:
        :return:
        """
        self.license.require(Features.TARGET_GROUPS)
        require_at_least_tech_admin_with_access_all(self.request_user)
        owner_id = get_owner_id(self.request_user)
        with Connection(self.current_shard_name) as (connection):
            query = (
                GroupsTable.delete()
                .where(GroupRow.group_id == group_id)
                .where(GroupRow.owner_id == owner_id)
            )
            result = connection.execute(query)
            if result.rowcount:
                event_query = create_event(
                    "group_deleted",
                    get_owner_id(self.request_user),
                    self.request_user["user_id"],
                    resource_type=EventResources.group,
                    resource_id=group_id,
                    shard=self.current_shard_name,
                )
                connection.execute(event_query)
        return Http204()

    def change_group(self, group_id, body, **_):
        """
        Permissions: tech_admin & access_all
        Request: patch
        :param group_id:
        :param body:
        :param _:
        :return:
        """
        self.license.require(Features.TARGET_GROUPS)
        require_at_least_tech_admin_with_access_all(self.request_user)
        owner_id = get_owner_id(self.request_user)
        group = ClsTargetGroup(
            ignored_required_keys=True, ignore_default_values=True, **body
        )
        update = dict()
        if hasattr(group, "name"):
            update["name"] = group.name
        if hasattr(group, "description"):
            update["description"] = group.description
        if update:
            with Connection(self.current_shard_name) as (connection):
                query = (
                    (GroupsTable.update(values=update))
                    .where(GroupRow.group_id == group_id)
                    .where(GroupRow.owner_id == owner_id)
                )
                result = connection.execute(query)
                if result.rowcount:
                    event_query = create_event(
                        "group_modified",
                        get_owner_id(self.request_user),
                        self.request_user["user_id"],
                        data=update,
                        resource_type=EventResources.group,
                        resource_id=group_id,
                        shard=self.current_shard_name,
                    )
                    connection.execute(event_query)
        return Http204()

    def list_targets(self, group_id, **_):
        """
        Lists targets assigned to a group
        Permissions: any_role & (access_all | access_group)
        :param group_id:
        :param _:
        :return:
        """
        self.license.require(Features.TARGET_GROUPS)
        joined_tables = []
        select_from, tail_filters = generate_target_query(
            self.request_user, joined_tables
        )
        if Target2GroupTable not in joined_tables:
            select_from = select_from.join(
                Target2GroupTable, Target2GroupRow.target_id == TargetRow.target_id
            )
        tail_filters.append(Target2GroupRow.group_id == group_id)
        tail_filters.append(TargetRow.deleted_at.is_(None))
        query = sql_select((TargetRow.target_id,)).select_from(select_from)
        query = apply_filters(query, tail_filters)
        target_id_list = []
        with Connection(self.current_shard_name) as (connection):
            for target in connection.execute(query).fetchall():
                target_id_list.append(target.target_id)

        return ClsTargetIdList(target_id_list=target_id_list)

    def change_targets(self, group_id, body, **_):
        """
        Changes targets to a specified group, ignore duplicates
        Permissions: tech_admin & access_all
        :param group_id:
        :param body:
        :return:
        """
        self.license.require(Features.TARGET_GROUPS)
        require_at_least_tech_admin_with_access_all(self.request_user)
        owner_id = get_owner_id(self.request_user)
        group_change_data = ClsGroupChangeTargetIdList(**body)
        add_target_id_list = list(set(group_change_data.add))
        remove_target_id_list = list(set(group_change_data.remove))
        targets_already_in_group = sql_select((Target2GroupRow.target_id,)).where(
            Target2GroupRow.group_id == group_id
        )
        add_target_id_and_group_query = (
            sql_select(
                (
                    TargetRow.target_id,
                    sqlalchemy.sql.expression.bindparam("group_id", group_id),
                    sqlalchemy.sql.expression.bindparam("owner_id", owner_id),
                )
            )
            .where(TargetRow.owner_id == owner_id)
            .where(TargetRow.deleted_at.is_(None))
            .where(TargetRow.target_id.in_(add_target_id_list))
            .where(sql_not(TargetRow.target_id.in_(targets_already_in_group)))
        )
        group_ownership_query = sql_select((GroupRow.group_id,)).where(
            GroupRow.owner_id == owner_id
        )
        with Connection(self.current_shard_name, create_transactions=True) as (
            connection
        ):
            group = connection.execute(group_ownership_query).fetchone()
            if not group:
                return Http403()
            query = Target2GroupTable.insert().from_select(
                (
                    Target2GroupRow.target_id,
                    Target2GroupRow.group_id,
                    Target2GroupRow.owner_id,
                ),
                add_target_id_and_group_query,
            )
            connection.execute(query)
            query = (
                Target2GroupTable.delete()
                .where(Target2GroupRow.target_id.in_(remove_target_id_list))
                .where(Target2GroupRow.group_id == group_id)
                .where(Target2GroupRow.owner_id == owner_id)
            )
            connection.execute(query)
            event_query = create_event(
                "group_targets_modified",
                get_owner_id(self.request_user),
                self.request_user["user_id"],
                data=dict(add=add_target_id_list, remove=remove_target_id_list),
                resource_type=EventResources.group,
                resource_id=group_id,
                shard=self.current_shard_name,
            )
            connection.execute(event_query)
        return Http204()

    def set_targets(self, group_id, body, **_):
        """
        Set's the targets within a group
        Permissions: tech_admin & (access_all | access_group)
        :param group_id:
        :param body:
        :param _:
        :return:
        """
        self.license.require(Features.TARGET_GROUPS)
        require_at_least_tech_admin(self.request_user)
        owner_id = get_owner_id(self.request_user)
        target_id_list = ClsTargetIdList(**body)
        target_id_list = list(set(target_id_list.target_id_list))
        target_id_and_group_query = (
            sql_select(
                (
                    TargetRow.target_id,
                    sqlalchemy.sql.expression.bindparam("group_id", group_id),
                    sqlalchemy.sql.expression.bindparam("owner_id", owner_id),
                )
            )
            .where(TargetRow.owner_id == owner_id)
            .where(TargetRow.deleted_at.is_(None))
            .where(TargetRow.target_id.in_(target_id_list))
        )
        group_ownership_query = sql_select((GroupRow.group_id,)).where(
            GroupRow.owner_id == owner_id
        )
        group_cleanup_query = Target2GroupTable.delete().where(
            Target2GroupRow.group_id == group_id
        )
        with Connection(self.current_shard_name, create_transactions=True) as (
            connection
        ):
            group = connection.execute(group_ownership_query).fetchone()
            if not group:
                return Http403()
            connection.execute(group_cleanup_query)
            query = Target2GroupTable.insert().from_select(
                (
                    Target2GroupRow.target_id,
                    Target2GroupRow.group_id,
                    Target2GroupRow.owner_id,
                ),
                target_id_and_group_query,
            )
            connection.execute(query)
            event_query = create_event(
                "group_targets_modified",
                get_owner_id(self.request_user),
                self.request_user["user_id"],
                data=dict(set=target_id_list),
                resource_type=EventResources.group,
                resource_id=group_id,
                shard=self.current_shard_name,
            )
            connection.execute(event_query)
        return Http204()
